/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.jta.atomikos;

import java.time.Duration;
import java.util.Properties;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * Bean 友好版本的
 * <a href="https://www.atomikos.com/Documentation/JtaProperties">Atomikos 配置属性</a>。
 * 支持基于 setter 的配置，且支持宽松的数据绑定。
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @since 1.2.0
 * @see #asProperties()
 */
@ConfigurationProperties(prefix = "spring.jta.atomikos.properties")
public class AtomikosProperties {

	/**
	 * 应该启动的事务管理器实现类。
	 */
	private String service;

	/**
	 * 事务允许的最大超时时间。
	 */
	private Duration maxTimeout = Duration.ofMillis(300000);

	/**
	 * JTA 事务的默认超时时间。
	 */
	private Duration defaultJtaTimeout = Duration.ofMillis(10000);

	/**
	 * 最大活跃事务数量。
	 */
	private int maxActives = 50;

	/**
	 * 是否启用磁盘日志记录。
	 */
	private boolean enableLogging = true;

	/**
	 * 事务管理器的唯一名称。默认是机器的IP地址。
	 * 如果计划对同一数据库运行多个事务管理器，必须设置此属性为唯一值。
	 */
	private String transactionManagerUniqueName;

	/**
	 * 是否在可能的情况下合并子事务。
	 */
	private boolean serialJtaTransactions = true;

	/**
	 * 是否允许子事务。
	 */
	private boolean allowSubTransactions = true;

	/**
	 * VM 关闭时是否强制关闭事务核心。
	 */
	private boolean forceShutdownOnVmExit;

	/**
	 * 正常关闭（非强制）时，等待事务完成的最长时间。
	 */
	private long defaultMaxWaitTimeOnShutdown = Long.MAX_VALUE;

	/**
	 * 事务日志文件的基础名称。
	 */
	private String logBaseName = "tmlog";

	/**
	 * 日志文件存储目录。默认为当前工作目录。
	 */
	private String logBaseDir;

	/**
	 * 检查点之间的间隔，以两次检查点之间的日志写入次数表示。
	 * 检查点能够减少日志文件大小，但会增加运行时开销。
	 */
	private long checkpointInterval = 500;

	/**
	 * 是否为参与资源的两阶段提交使用不同（并发）线程。
	 */
	private boolean threadedTwoPhaseCommit;

	private final Recovery recovery = new Recovery();

	/**
	 * 指定应启动的事务管理器实现。
	 * 没有默认值，必须设置。
	 * 通常应设置为 {@literal com.atomikos.icatch.standalone.UserTransactionServiceFactory}。
	 * @param service 事务管理器实现类名
	 */
	public void setService(String service) {
		this.service = service;
	}

	public String getService() {
		return this.service;
	}

	/**
	 * 指定事务允许的最大超时时间。默认为 {@literal 300000} 毫秒（5 分钟）。
	 * 这意味着调用 UserTransaction.setTransactionTimeout() 设置的值若超过此处配置的值，
	 * 实际超时时间将被限制为该最大值。
	 * @param maxTimeout 最大超时时间
	 */
	public void setMaxTimeout(Duration maxTimeout) {
		this.maxTimeout = maxTimeout;
	}

	public Duration getMaxTimeout() {
		return this.maxTimeout;
	}

	/**
	 * JTA 事务的默认超时时间（可选，默认为 {@literal 10000} 毫秒）。
	 * @param defaultJtaTimeout 默认 JTA 超时时间
	 */
	public void setDefaultJtaTimeout(Duration defaultJtaTimeout) {
		this.defaultJtaTimeout = defaultJtaTimeout;
	}

	public Duration getDefaultJtaTimeout() {
		return this.defaultJtaTimeout;
	}

	/**
	 * 指定最大活动事务数。默认为 {@literal 50}。
	 * 如果设置为负数表示无限制。
	 * 如果并发运行的事务数已达到该值，再调用 {@code UserTransaction.begin()} 时将抛出
	 * {@code IllegalStateException} 异常，并显示错误信息："Max number of active transactions reached"。
	 * @param maxActives 最大活动事务数
	 */
	public void setMaxActives(int maxActives) {
		this.maxActives = maxActives;
	}

	public int getMaxActives() {
		return this.maxActives;
	}

	/**
	 * 指定是否启用磁盘日志记录。默认为 true。此功能在进行 JUnit 测试或代码性能分析时很有用，
	 * 可以避免事务管理器的活动成为性能瓶颈，但在生产环境中绝不应关闭，否则无法保证数据一致性。
	 * @param enableLogging 是否启用日志记录
	 */
	public void setEnableLogging(boolean enableLogging) {
		this.enableLogging = enableLogging;
	}

	public boolean isEnableLogging() {
		return this.enableLogging;
	}

	/**
	 * 指定事务管理器的唯一名称。默认使用机器的 IP 地址。
	 * 如果计划在同一个数据库上运行多个事务管理器，必须将此属性设置为唯一值，
	 * 否则可能会遇到微妙的重复事务 ID（XID）问题（示例：
	 * {@literal https://fogbugz.atomikos.com/default.asp?community.6.2225.7}）。
	 * 如果多个实例需要使用相同的属性文件，确保此属性唯一最简单的方法是引用在虚拟机启动时指定的系统属性。
	 * @param uniqueName 唯一名称
	 */
	public void setTransactionManagerUniqueName(String uniqueName) {
		this.transactionManagerUniqueName = uniqueName;
	}

	public String getTransactionManagerUniqueName() {
		return this.transactionManagerUniqueName;
	}

	/**
	 * 指定是否在可能的情况下合并子事务。默认为 true。
	 * 当为 false 时，将不会尝试对不同但相关的子事务调用 {@code XAResource.start(TM_JOIN)}。
	 * 此设置对同一事务内的资源访问无影响。如果未使用子事务，可以忽略此设置。
	 * @param serialJtaTransactions 是否支持串行 JTA 事务
	 */
	public void setSerialJtaTransactions(boolean serialJtaTransactions) {
		this.serialJtaTransactions = serialJtaTransactions;
	}

	public boolean isSerialJtaTransactions() {
		return this.serialJtaTransactions;
	}

	public void setAllowSubTransactions(boolean allowSubTransactions) {
		this.allowSubTransactions = allowSubTransactions;
	}

	public boolean isAllowSubTransactions() {
		return this.allowSubTransactions;
	}

	/**
	 * 指定是否在虚拟机关闭时强制关闭事务核心。默认值为 false。
	 * @param forceShutdownOnVmExit 是否在 VM 退出时强制关闭
	 */
	public void setForceShutdownOnVmExit(boolean forceShutdownOnVmExit) {
		this.forceShutdownOnVmExit = forceShutdownOnVmExit;
	}

	public boolean isForceShutdownOnVmExit() {
		return this.forceShutdownOnVmExit;
	}

	/**
	 * 指定正常关闭（非强制）时，等待事务完成的最长时间。默认值为 {@literal Long.MAX_VALUE}。
	 * @param defaultMaxWaitTimeOnShutdown 关闭时的最大等待时间
	 */
	public void setDefaultMaxWaitTimeOnShutdown(long defaultMaxWaitTimeOnShutdown) {
		this.defaultMaxWaitTimeOnShutdown = defaultMaxWaitTimeOnShutdown;
	}

	public long getDefaultMaxWaitTimeOnShutdown() {
		return this.defaultMaxWaitTimeOnShutdown;
	}

	/**
	 * 指定事务日志文件的基础名称，默认为 {@literal tmlog}。
	 * 事务日志将存储在以该名称为前缀、附加编号并以 {@literal .log} 为扩展名的文件中。
	 * 在检查点时会创建一个新的事务日志文件，并将编号递增。
	 * @param logBaseName 日志基础名称
	 */
	public void setLogBaseName(String logBaseName) {
		this.logBaseName = logBaseName;
	}

	public String getLogBaseName() {
		return this.logBaseName;
	}

	/**
	 * 指定日志文件存储的目录，默认为当前工作目录。
	 * 该目录应为稳定存储，例如 SAN、RAID，或至少是备份位置。
	 * 事务日志文件对于在故障情况下保证数据一致性而言与数据本身一样重要。
	 * @param logBaseDir 日志基础目录
	 */
	public void setLogBaseDir(String logBaseDir) {
		this.logBaseDir = logBaseDir;
	}

	public String getLogBaseDir() {
		return this.logBaseDir;
	}

	/**
	 * 指定检查点之间的间隔。检查点可以减少日志文件的大小，但会带来一定的运行时开销。
	 * 默认值为 {@literal 500}。
	 * @param checkpointInterval 检查点间隔
	 */
	public void setCheckpointInterval(long checkpointInterval) {
		this.checkpointInterval = checkpointInterval;
	}

	public long getCheckpointInterval() {
		return this.checkpointInterval;
	}

	/**
	 * 指定是否对参与资源的两阶段提交使用不同（并发）的线程。
	 * 设置为 {@literal true} 表示提交更高效，因为等待确认的过程是并行进行的，默认值为 {@literal true}。
	 * 如果设置为 {@literal false}，提交将按事务中资源访问的顺序进行。
	 * @param threadedTwoPhaseCommit 是否启用多线程的两阶段提交
	 */
	public void setThreadedTwoPhaseCommit(boolean threadedTwoPhaseCommit) {
		this.threadedTwoPhaseCommit = threadedTwoPhaseCommit;
	}

	public boolean isThreadedTwoPhaseCommit() {
		return this.threadedTwoPhaseCommit;
	}

	public Recovery getRecovery() {
		return this.recovery;
	}

	/**
	 * 将属性转换为可供 Atomikos 使用的 {@link Properties} 对象。
	 * @return 属性对象
	 */
	public Properties asProperties() {
		Properties properties = new Properties();
		set(properties, "service", getService());
		set(properties, "max_timeout", getMaxTimeout());
		set(properties, "default_jta_timeout", getDefaultJtaTimeout());
		set(properties, "max_actives", getMaxActives());
		set(properties, "enable_logging", isEnableLogging());
		set(properties, "tm_unique_name", getTransactionManagerUniqueName());
		set(properties, "serial_jta_transactions", isSerialJtaTransactions());
		set(properties, "allow_subtransactions", isAllowSubTransactions());
		set(properties, "force_shutdown_on_vm_exit", isForceShutdownOnVmExit());
		set(properties, "default_max_wait_time_on_shutdown", getDefaultMaxWaitTimeOnShutdown());
		set(properties, "log_base_name", getLogBaseName());
		set(properties, "log_base_dir", getLogBaseDir());
		set(properties, "checkpoint_interval", getCheckpointInterval());
		set(properties, "threaded_2pc", isThreadedTwoPhaseCommit());
		Recovery recovery = getRecovery();
		set(properties, "forget_orphaned_log_entries_delay", recovery.getForgetOrphanedLogEntriesDelay());
		set(properties, "recovery_delay", recovery.getDelay());
		set(properties, "oltp_max_retries", recovery.getMaxRetries());
		set(properties, "oltp_retry_interval", recovery.getRetryInterval());
		return properties;
	}

	private void set(Properties properties, String key, Object value) {
		String id = "com.atomikos.icatch." + key;
		if (value != null && !properties.containsKey(id)) {
			properties.setProperty(id, asString(value));
		}
	}

	private String asString(Object value) {
		if (value instanceof Duration) {
			return String.valueOf(((Duration) value).toMillis());
		}
		return value.toString();
	}

	/**
	 * 恢复（Recovery）相关配置。
	 */
	public static class Recovery {

		/**
		 * 延迟多久后清理待处理（“孤立”的）日志条目。
		 */
		private Duration forgetOrphanedLogEntriesDelay = Duration.ofMillis(86400000);

		/**
		 * 两次恢复扫描之间的间隔时间。
		 */
		private Duration delay = Duration.ofMillis(10000);

		/**
		 * 提交事务前的最大重试次数，超过则抛出异常。
		 */
		private int maxRetries = 5;

		/**
		 * 每次重试之间的间隔时间。
		 */
		private Duration retryInterval = Duration.ofMillis(10000);

		public Duration getForgetOrphanedLogEntriesDelay() {
			return this.forgetOrphanedLogEntriesDelay;
		}

		public void setForgetOrphanedLogEntriesDelay(Duration forgetOrphanedLogEntriesDelay) {
			this.forgetOrphanedLogEntriesDelay = forgetOrphanedLogEntriesDelay;
		}

		public Duration getDelay() {
			return this.delay;
		}

		public void setDelay(Duration delay) {
			this.delay = delay;
		}

		public int getMaxRetries() {
			return this.maxRetries;
		}

		public void setMaxRetries(int maxRetries) {
			this.maxRetries = maxRetries;
		}

		public Duration getRetryInterval() {
			return this.retryInterval;
		}

		public void setRetryInterval(Duration retryInterval) {
			this.retryInterval = retryInterval;
		}

	}

}
